package com.bobi.tfca.config;

import cn.hutool.core.lang.Pair;
import com.bobi.tfca.model.constant.RedisConstant;
import com.bobi.tfca.model.entity.MemberEntity;
import com.bobi.tfca.model.vo.MemberVO;
import com.bobi.tfca.service.MemberService;
import com.bobi.tfca.web.security.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;

/**
 * security配置
 */
@Slf4j
@Component
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

    @Value("${time.token-expiration:7}")
    // token有效期
    private Integer tokenExpiration;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private MemberService memberService;

    private static final String[] permitUrls = {"/login","/member/logout", "/member/getMemberMenus", "/member/registerMember", "/swagger-*/*", "/member/getMemberByAccount", "/member/resetPassword",
            "/resource/uploadImage", "/resource/getMemberRights", "/resource/getAssRules", "/resource/sendEmailCode4Register/*", "/resource/sendEmailCode2Exist/*", "/resource/getOrgStructurePicture",
            "/activity/findAll", "/article/findAllCultures", "/article/getArticleDetails/*"};

    /**
     * security过滤器链的配置
     *
     * @param http 请求
     * @return 过滤器链
     * @throws Exception
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                // 无状态会话
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 禁用缓存控制和禁用帧
                .headers().cacheControl().disable().frameOptions().disable().and()
                // 跨域资源共享（CORS）配置
                .cors().disable()
                // 跨站请求伪造（CSRF）配置
                .csrf().disable()
                // 登录
                .formLogin().and()
                // 启用退出登录功能。
                .logout().logoutUrl("/logout").and()
                // 配置自定义的身份验证提供者，用于处理身份验证
                .authenticationProvider(authenticationProvider())
                // 在 UsernamePasswordAuthenticationFilter 前添加自定义的登录认证过滤器
                .addFilterBefore(loginAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                // 在 BasicAuthenticationFilter 前添加自定义的 JWT 认证过滤器
                .addFilterBefore(jwtAuthenticationFilter(), BasicAuthenticationFilter.class)
                // 授权
                .authorizeRequests()
                // 不拦截跨域检测
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                // 未登录/登录用户可访问的路径
                .antMatchers(permitUrls).permitAll()
                .anyRequest().authenticated()// 其他所有请求需要身份验证
                // .anyRequest().permitAll()// 没有权限,所有的都可以访问
                // 安全策略,控制每个url链接是否有权限访问.不通过注解配置
//                .anyRequest().access((authentication, content) -> {
//                    try {
//                        HttpServletRequest request = (HttpServletRequest) content.getRequest();
//                        String uri = request.getRequestURI();
//                        // 需要登录才能访问的路径
//                        if (loginAllowUrls.contains(uri)) {
//                            return new AuthorizationDecision(true);
//                        }
//                        if (authentication instanceof AnonymousAuthenticationToken) {
//                            return new AuthorizationDecision(false);
//                        }
//                        JwtToken token = (JwtToken) authentication;
//                        // 用户名和用户访问的url
//                        Pair<Long, String> principal = (Pair<Long, String>) token.getPrincipal();
//                        Long id = principal.getFirst();
//                        String name = principal.getSecond();
//                        Boolean member = redisTemplate.opsForSet().isMember(StandardRedis.userPwdExpireKeySet, name);
//                        if (member != null && member) {
//                            // 密码过期或第一次登录, 其他页面不可访问, 只能访问loginAllowUrls的页面
//                            return new AuthorizationDecision(false);
//                        }
//                        // boolean isHasAuthentication = checkUserAuthentication(id, uri);
//                        boolean isHasAuthentication = true; // 每个接口的访问权限（登录后）
//                        return new AuthorizationDecision(isHasAuthentication);
//                    } catch (Exception ex) {
//                        log.error(ex.getMessage());
//                        return new AuthorizationDecision(false);
//                    }
//                })
                .and()
                .exceptionHandling()
                .authenticationEntryPoint((request, response, e) -> {
                    response.setStatus(HttpStatus.FORBIDDEN.value());
                    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                    response.setCharacterEncoding(StandardCharsets.UTF_8.name());
                    response.getWriter().write("{\"code\":" + HttpServletResponse.SC_UNAUTHORIZED + ",\"msg\":\"未登录\"}");
                    response.getWriter().flush();
                    log.error(request.getRequestURI() + " " + e.getMessage());
                    // log.error(e.getMessage(), e);
                })
                .accessDeniedHandler((request, response, e) -> {
                    response.setStatus(HttpStatus.UNAUTHORIZED.value());
                    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                    response.setCharacterEncoding(StandardCharsets.UTF_8.name());
                    response.getWriter().write("{\"code\":" + HttpServletResponse.SC_FORBIDDEN + ",\"msg\":\"无权访问\"}");
                    response.getWriter().flush();
                });
        return http.build();
    }

    /**
     * 使用BCryptPasswordEncoder加密密码
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 自定义认证类
     */
    @Bean
    public AuthenticationProvider authenticationProvider() {
        return new CustomAuthenticationProvider(passwordEncoder(), userDetailsService);
    }

    /**
     * 设置登录校验，登录路径，登录成功，和登录失败后做什么
     */
    @Bean
    public LoginAuthenticationFilter loginAuthenticationFilter() {
        LoginAuthenticationFilter filter = new LoginAuthenticationFilter();
        filter.setAuthenticationManager(authentication -> authenticationProvider().authenticate(authentication));
        // 设置登录路径
        filter.setRequiresAuthenticationRequestMatcher(request -> request.getMethod().equals(HttpMethod.POST.name()) && request.getRequestURI().equals("/login"));
        // 登录成功处理器
        filter.setAuthenticationSuccessHandler((request, response, authentication) -> {
            UserDetailsWrapper detailsWrapper = (UserDetailsWrapper) authentication.getPrincipal();
            MemberEntity member = detailsWrapper.getMember();
            String jwt = Jwt.generateJWT(member.getMemberId(), member.getMemberName());
            stringRedisTemplate.opsForValue().set(RedisConstant.USER_JWT_PREFIX + member.getMemberId(), jwt);
            // stringRedisTemplate.opsForValue().set(RedisConstant.USER_JWT_PREFIX + member.getMemberId(), jwt, tokenExpiration * 24 * 60 * 60 * 1000, TimeUnit.MILLISECONDS);
            // 查询会员会龄以及身份
            MemberVO memberVO = memberService.getRoleAndMemberAge(member);

            response.setStatus(HttpStatus.OK.value());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.getWriter().write("{\"code\":200,\"msg\":\"success\",\"data\":{\"jwtToken\":\"" + jwt + "\",\"memberId\":\"" + memberVO.getMemberId() + "\", \"telephone\":\"" + memberVO.getTelephone() + "\", \"headPhoto\":\"" + memberVO.getPicture() + "\", \"memberAge\":\"" + memberVO.getMemberAge() + "\",\"memberName\":\"" + memberVO.getMemberName() + "\",\"role\":\"" + memberVO.getRole() + "\"}}");
            response.getWriter().flush();
        });
        // 登录失败处理器
        filter.setAuthenticationFailureHandler((request, response, exception) -> {
            String message = formatLoginFailureMsg(exception);
            response.setStatus(HttpStatus.OK.value());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.getWriter().write("{\"code\":600,\"msg\":\"" + message + "\",\"data\":null}");
            response.getWriter().flush();
        });
        filter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());
        return filter;
    }


    /**
     * 格式化登录失败信息
     *
     * @param e 登录失败异常
     */
    private String formatLoginFailureMsg(AuthenticationException e) {
        if (e instanceof BadCredentialsException) {
            String message = e.getMessage();
            if (message.contains("is null") || "Bad credentials".equals(message)) {
                return "用户名或密码错误";
            }
            return message;
        } else if (e instanceof DisabledException) {
            return "账号已被禁用";
        } else if (e instanceof LockedException) {
            return "账号已被锁定";
        } else if (e instanceof CredentialsExpiredException) {
            return "密码已过期，请修改密码";
        } else if (e instanceof AccountExpiredException) {
            return "账号已过期";
        } else {
            return "登录失败";
        }
    }

    /**
     * 创建JWT登录过滤器
     * 暂时是永不过期策略
     */
    @Bean
    public BasicAuthenticationFilter jwtAuthenticationFilter() {
        return new BasicAuthenticationFilter(authentication -> {
            try {
                String jwt = authentication.getCredentials().toString();
                Pair<Integer, String> parsedJwt = Jwt.parseJWT(jwt);
                Integer id = parsedJwt.getKey();
                String memberName = parsedJwt.getValue();
                String redisKey = RedisConstant.USER_JWT_PREFIX + id;
                if (Boolean.FALSE.equals(stringRedisTemplate.hasKey(redisKey))) {
                    throw new BadCredentialsException("用户" + memberName + "登录过期");
                }
                // Long expire = stringRedisTemplate.getExpire(redisKey);
                // if (expire != null) {
                //     log.info("用户剩余过期时间：" + expire / 30 + "分钟");
                // }
                return new JwtToken(id, memberName, jwt);
            } catch (ParseException e) {
                throw new RuntimeException(e);
            }
        }) {
            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
                //获取请求头中的JWT
                String jwt = request.getHeader(HttpHeaders.AUTHORIZATION);
                if (jwt != null) {
                    try {
                        JwtToken token = new JwtToken(jwt);
                        Authentication authentication = getAuthenticationManager().authenticate(token);
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                        // Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
                    } catch (Exception ex) {
                        log.error(ex.getMessage());
                        SecurityContextHolder.clearContext();
                    }
                }
                chain.doFilter(request, response);
            }
        };
    }
}
